/*******************************************************************************
#                                                                              #
#      MJPG-streamer allows to stream JPG frames from an input-plugin          #
#      to several output plugins                                               #
#                                                                              #
#      Copyright (C) 2007 Tom Stöveken                                         #
#                                                                              #
# This program is free software; you can redistribute it and/or modify         #
# it under the terms of the GNU General Public License as published by         #
# the Free Software Foundation; version 2 of the License.                      #
#                                                                              #
# This program is distributed in the hope that it will be useful,              #
# but WITHOUT ANY WARRANTY; without even the implied warranty of               #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                #
# GNU General Public License for more details.                                 #
#                                                                              #
# You should have received a copy of the GNU General Public License            #
# along with this program; if not, write to the Free Software                  #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA    #
#                                                                              #
*******************************************************************************/

/*
  This output plugin is based on code from output_file.c
  Writen by Dimitrios Zachariadis
  Modified to support video streaming by adding frame transmission
  Version 0.2, [Current Date]
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <resolv.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <getopt.h>
#include <pthread.h>
#include <fcntl.h>
#include <time.h>
#include <syslog.h>

#include <dirent.h>

#include "../../utils.h"
#include "../../mjpg_streamer.h"

#define OUTPUT_PLUGIN_NAME "UDP output plugin"
#define MAX_UDP_PACKET 1400  // 最大UDP包大小，避免MTU问题
#define FRAME_MAGIC 0xDEADBEEF  // 帧起始标识
static int streaming_enabled = 0;  // 0: 不发送流, 1: 发送流
static pthread_mutex_t stream_mutex = PTHREAD_MUTEX_INITIALIZER;  
static pthread_t worker;
static globals *pglobal;
static int fd, delay, max_frame_size;
static char *folder = "/tmp";
static unsigned char *frame = NULL;
static char *command = NULL;
static int input_number = 0;

// UDP配置
static int port = 0;
static char *dest_ip = NULL;  // 新增：目标IP
static int dest_port =0;     // 新增：目标端口
static int stream_fps = 10;   // 新增：流帧率

/******************************************************************************
Description.: print a help message
Input Value.: -
Return Value: -
******************************************************************************/
void help(void)
{
    fprintf(stderr, " ---------------------------------------------------------------\n" \
            " Help for output plugin..: "OUTPUT_PLUGIN_NAME"\n" \
            " ---------------------------------------------------------------\n" \
            " The following parameters can be passed to this plugin:\n\n" \
            " [-f | --folder ]........: folder to save pictures\n" \
            " [-d | --delay ].........: delay after saving pictures in ms\n" \
            " [-c | --command ].......: execute command after saveing picture\n" \
            " [-p | --port ]..........: UDP port to listen for picture requests\n" \
            " [-i | --input ].........: read frames from the specified input plugin\n" \
            " [-t | --dest-ip ].......: destination IP for video streaming\n" \
            " [-r | --dest-port ].....: destination port for video streaming\n" \
            " [-s | --fps ]...........: streaming frame rate (default: 10)\n" \
            " ---------------------------------------------------------------\n");
}

/******************************************************************************
Description.: 发送视频帧到目标地址
Input Value.: socket描述符、帧数据、帧大小
Return Value: 0成功，-1失败
******************************************************************************/
static int send_video_frame(int sd, unsigned char *frame_data, int frame_size)
{
    // if (!dest_ip || dest_port <= 0) 
    // {
    //     printf("%d\n",dest_port);
    //     return -1;
    // }
     char *dest_ip1="192.168.0.193";
     int port=8888;
    struct sockaddr_in dest_addr;
    bzero(&dest_addr, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(port);
    if (inet_aton(dest_ip1, &dest_addr.sin_addr) == 0) {
        OPRINT("Invalid destination IP address\n");
        return -1;
    }
    // 构建帧头部：魔术字 + 总长度 + 分片数 + 当前分片
    int total_packets = (frame_size + MAX_UDP_PACKET - 1) / MAX_UDP_PACKET;
    int packet_num=0;
   
    for (packet_num = 0; packet_num < total_packets; packet_num++) 
    {
        // 计算当前分片大小和偏移量
        int offset = packet_num * MAX_UDP_PACKET;
        int chunk_size = (packet_num == total_packets - 1) ? 
                         (frame_size - offset) : MAX_UDP_PACKET;
        // 构建数据包：头部(16字节) + 数据
        unsigned char packet[MAX_UDP_PACKET + 16];
        *(uint32_t *)&packet[0] = htonl(FRAME_MAGIC);       // 魔术字
        *(uint32_t *)&packet[4] = htonl(frame_size);         // 总长度
        *(uint32_t *)&packet[8] = htonl(total_packets);      // 总分片数
        *(uint32_t *)&packet[12] = htonl(packet_num);        // 当前分片
        // 复制数据到数据包
        memcpy(packet + 16, frame_data + offset, chunk_size);
        
        // 发送数据包
        if (sendto(sd, packet, chunk_size + 16, 0, 
                  (struct sockaddr*)&dest_addr, sizeof(dest_addr)) < 0) 
        {
            perror("sendto failed");
            return -1;
        }
    }
    printf("send ok\n");
    return 0;
}

/******************************************************************************
Description.: clean up allocated resources
Input Value.: unused argument
Return Value: -
******************************************************************************/
void worker_cleanup(void *arg)
{
    static unsigned char first_run = 1;

    if(!first_run) {
        DBG("already cleaned up resources\n");
        return;
    }

    first_run = 0;
    OPRINT("cleaning up resources allocated by worker thread\n");

    if(frame != NULL) {
        free(frame);
    }
    close(fd);
    pthread_mutex_destroy(&stream_mutex);
}

/******************************************************************************
Description.: this is the main worker thread
              it loops forever, grabs frames and handles both snapshot requests
              and continuous video streaming
Input Value.:
Return Value:
******************************************************************************/
void *worker_thread(void *arg)
{
    int ok = 1, frame_size = 0, rc = 0;
    char buffer1[1024] = {0};
    unsigned char *tmp_framebuffer = NULL;
    // 新增：创建视频流发送专用socket
    int stream_socket = socket(PF_INET, SOCK_DGRAM, 0);
    if (stream_socket < 0) 
    {
        perror("Failed to create stream socket");
        return NULL;
    }

    /* set cleanup handler to cleanup allocated resources */
    pthread_cleanup_push(worker_cleanup, NULL);
   
    // UDP服务器初始化（用于接收指令）
    if(port <= 0) 
    {
        OPRINT("a valid UDP port must be provided\n");
        return NULL;
    }
    struct sockaddr_in addr;
    int sd;
    int bytes;
    unsigned int addr_len = sizeof(addr);
    char udpbuffer[1024] = {0};
    sd = socket(PF_INET, SOCK_DGRAM, 0);
     
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(port);
    if(bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0)
        perror("bind");

    // 初始化互斥锁
    pthread_mutex_init(&stream_mutex, NULL);

    while(ok >= 0 && !pglobal->stop) 
    {
        DBG("waiting for a UDP message\n");
        
        // 接收UDP指令
        memset(udpbuffer, 0, sizeof(udpbuffer));
        bytes = recvfrom(sd, udpbuffer, sizeof(udpbuffer), 0, (struct sockaddr*)&addr, &addr_len);
        if (bytes < 0) {
            perror("recvfrom failed");
            continue;
        }
        printf("Received command: %s\n", udpbuffer);

        // 处理指令：判断是否为get_stream
        // 去除字符串中的换行符和空格，确保准确匹配
        udpbuffer[strcspn(udpbuffer, "\r\n ")] = '\0';
        if (strcmp(udpbuffer, "get_stream") == 0) {
            // 标记需要发送视频流
            pthread_mutex_lock(&stream_mutex);
            streaming_enabled = 1;
            pthread_mutex_unlock(&stream_mutex);
            // 回传确认消息
            //sendto(sd, "stream starting", 15, 0, (struct sockaddr*)&addr, sizeof(addr));
            OPRINT("Received get_stream command, starting video transmission\n");
        }

        // 获取最新帧数据
        DBG("waiting for fresh frame\n");
        pthread_mutex_lock(&pglobal->in[input_number].db);
        pthread_cond_wait(&pglobal->in[input_number].db_update, &pglobal->in[input_number].db);

        /* 读取帧大小 */
        frame_size = pglobal->in[input_number].size;
        printf("当前帧大小: %d\n", frame_size); 
        /* 检查并调整缓冲区大小 */
        if(frame_size > max_frame_size) {
            DBG("increasing buffer size to %d\n", frame_size);
            max_frame_size = frame_size + (1 << 16);
            if((tmp_framebuffer = realloc(frame, max_frame_size)) == NULL) {
                pthread_mutex_unlock(&pglobal->in[input_number].db);
                LOG("not enough memory\n");
                return NULL;
            }
            frame = tmp_framebuffer;
        }

        /* 复制帧数据到本地缓冲区 */
        memcpy(frame, pglobal->in[input_number].buf, frame_size);
        pthread_mutex_unlock(&pglobal->in[input_number].db);

        /* 处理文件保存（原有逻辑） */
        // if(strlen(udpbuffer) > 0 && strcmp(udpbuffer, "get_stream") != 0 && strcmp(udpbuffer, "stop_stream") != 0) {
        //     DBG("writing file: %s\n", udpbuffer);
        //     if((fd = open(udpbuffer, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) {
        //         OPRINT("could not open the file %s\n", udpbuffer);
        //         return NULL;
        //     }
        //     if(write(fd, frame, frame_size) < 0) {
        //         OPRINT("could not write to file %s\n", udpbuffer);
        //         perror("write()");
        //         close(fd);
        //         return NULL;
        //     }
        //     close(fd);
        // }

        // // 回传客户端消息（除流控制指令外）
        // if (strcmp(udpbuffer, "get_stream") != 0 && strcmp(udpbuffer, "stop_stream") != 0) {
        //     sendto(sd, udpbuffer, bytes, 0, (struct sockaddr*)&addr, sizeof(addr));
        // }

        // /* 执行配置的命令（原有逻辑） */
        // if(command != NULL && strlen(udpbuffer) > 0) {
        //     memset(buffer1, 0, sizeof(buffer1));
        //     snprintf(buffer1, sizeof(buffer1), "%s \"%s\"", command, udpbuffer);
        //     DBG("calling command %s", buffer1);
        //     if((rc = setenv("MJPG_FILE", udpbuffer, 1)) != 0) {
        //         LOG("setenv failed (return value %d)\n", rc);
        //     }
        //     if((rc = system(buffer1)) != 0) {
        //         LOG("command failed (return value %d)\n", rc);
        //     }
        // }

        pthread_mutex_lock(&stream_mutex);
        int should_send = streaming_enabled;
        pthread_mutex_unlock(&stream_mutex);

        if(should_send) {
            // 调用视频帧发送函数
            send_video_frame(stream_socket, frame, frame_size);
            
            //可选：如果只需要发送一帧，这里可以重置streaming_enabled
            // pthread_mutex_lock(&stream_mutex);
            // streaming_enabled = 0;
            // pthread_mutex_unlock(&stream_mutex);
        }

        /* 延迟处理 */
        if(delay > 0) {
            usleep(1000 * delay);
        }
    }

    // 关闭socket
    close(sd);
    close(stream_socket);  // 关闭视频流专用socket

    /* 清理资源 */
    pthread_cleanup_pop(1);
    return NULL;
}

/*** plugin interface functions ***/
/******************************************************************************
Description.: initialise plugin
Input Value.: parameters
Return Value: 0 if ok, non-zero otherwise
******************************************************************************/
int output_init(output_parameter *param)
{
    int i;

    delay = 0;

    param->argv[0] = OUTPUT_PLUGIN_NAME;

    /* show all parameters for DBG purposes */
    for(i = 0; i < param->argc; i++) {
        DBG("argv[%d]=%s\n", i, param->argv[i]);
    }

    reset_getopt();
    while(1) {
        int option_index = 0, c = 0;
        static struct option long_options[] = {
            {"h", no_argument, 0, 0
            },
            {"help", no_argument, 0, 0},
            {"f", required_argument, 0, 0},
            {"folder", required_argument, 0, 0},
            {"d", required_argument, 0, 0},
            {"delay", required_argument, 0, 0},
            {"c", required_argument, 0, 0},
            {"command", required_argument, 0, 0},
            {"p", required_argument, 0, 0},
            {"port", required_argument, 0, 0},
            {"i", required_argument, 0, 0},
            {"input", required_argument, 0, 0},
            {0, 0, 0, 0}
        };

        c = getopt_long_only(param->argc, param->argv, "", long_options, &option_index);

        /* no more options to parse */
        if(c == -1) break;

        /* unrecognized option */
        if(c == '?') {
            help();
            return 1;
        }

        switch(option_index) {
            /* h, help */
        case 0:
        case 1:
            DBG("case 0,1\n");
            help();
            return 1;
            break;

            /* f, folder */
        case 2:
        case 3:
            DBG("case 2,3\n");
            folder = malloc(strlen(optarg) + 1);
            strcpy(folder, optarg);
            if(folder[strlen(folder)-1] == '/')
                folder[strlen(folder)-1] = '\0';
            break;

            /* d, delay */
        case 4:
        case 5:
            DBG("case 4,5\n");
            delay = atoi(optarg);
            break;

            /* c, command */
        case 6:
        case 7:
            DBG("case 6,7\n");
            command = strdup(optarg);
            break;
            /* p, port */
        case 8:
        case 9:
            DBG("case 8,9\n");
            port = atoi(optarg);
            break;
            /* i, input */
        case 10:
        case 11:
            DBG("case 10,11\n");
            input_number = atoi(optarg);
            break;
        }
    }

    pglobal = param->global;
    if(!(input_number < pglobal->incnt)) {
        OPRINT("ERROR: the %d input_plugin number is too much only %d plugins loaded\n", input_number, pglobal->incnt);
        return 1;
    }
    OPRINT("input plugin.....: %d: %s\n", input_number, pglobal->in[input_number].plugin);
    OPRINT("output folder.....: %s\n", folder);
    OPRINT("delay after save..: %d\n", delay);
    OPRINT("command...........: %s\n", (command == NULL) ? "disabled" : command);
    if(port > 0) {
        OPRINT("UDP port..........: %d\n", port);
    } else {
        OPRINT("UDP port..........: %s\n", "disabled");
    }
     if (pthread_mutex_init(&stream_mutex, NULL) != 0) {
        OPRINT("Failed to initialize stream mutex\n");
        return 1;
    }
    return 0;
}

/******************************************************************************
Description.: stops the worker thread
Input Value.: -
Return Value: always 0
******************************************************************************/
int output_stop(int id)
{
    DBG("will cancel worker thread\n");
    pthread_cancel(worker);
    return 0;
}

/******************************************************************************
Description.: creates and starts the worker thread
Input Value.: -
Return Value: always 0
******************************************************************************/
int output_run(int id)
{
    DBG("launching worker thread\n");
    pthread_create(&worker, 0, worker_thread, NULL);
    pthread_detach(worker);
    return 0;
}